package city.spring.utils;

import org.joda.time.DateTime;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * 日期处理工具
 *
 * @author HouKunLin
 * @date 2020/7/23 0023 17:04
 */
public class DateUtils {
    /**
     * Java 8 日期时间格式化对象列表
     */
    private static final List<DateTimeFormatter> DATE_TIME_FORMATTERS = new ArrayList<>();
    /**
     * 普通日期时间格式化对象列表
     */
    private static final List<DateFormat> DATE_FORMATS = new ArrayList<>();

    static {
        // 使日期格式能够兼容我们常使用的格式
        String[] formats = new String[]{
                "yyyy-MM-dd HH:mm:ss.SSS",
                "yyyy-MM-dd HH:mm:ss",
                "yyyy-MM-dd",
                "HH:mm:ss",
        };
        for (String format : formats) {
            DATE_FORMATS.add(new SimpleDateFormat(format));
            DATE_TIME_FORMATTERS.add(DateTimeFormatter.ofPattern(format));
        }
    }

    /**
     * 使用自定义的日期格式来解析为 Date。
     * 仅支持 {@link #DATE_FORMATS} 中定义的格式。
     *
     * @param source 日期字符串
     * @return Date
     */
    @Nullable
    public static Date parseDate(@NonNull String source) {
        source = source.trim();
        if (source.isEmpty()) {
            return null;
        }
        for (DateFormat dateFormat : DATE_FORMATS) {
            try {
                return dateFormat.parse(source);
            } catch (ParseException ignored) {
            }
        }
        throw new RuntimeException("无法解析" + source + "为" + Date.class.getName() + "类型");
    }

    /**
     * 使用自定义的日期格式来解析为本地时间，会优先使用默认的 ISO 标准格式来解析，如果解析失败才使用自定义的格式来解析。
     * 可支持 <code>yyyy-MM-dd'T'HH:mm:ss</code> 和 {@link #DATE_FORMATS} 中定义的格式。
     *
     * @param source         日期字符串
     * @param defaultParse   默认的解析方法，使用 ISO 标准格式解析
     * @param formatterParse 需要传入 DateTimeFormatter 的解析方法，使用自定义的格式来解析
     * @param <T>            本地实际类型：LocalDateTime / LocalDate / LocalTime
     * @return LocalDateTime / LocalDate / LocalTime
     */
    @Nullable
    public static <T> T parseDate(@NonNull String source, Function<CharSequence, T> defaultParse, BiFunction<CharSequence, DateTimeFormatter, T> formatterParse) {
        source = source.trim();
        if (source.isEmpty()) {
            return null;
        }
        try {
            return defaultParse.apply(source);
        } catch (Exception e1) {
            for (DateTimeFormatter formatter : DATE_TIME_FORMATTERS) {
                try {
                    return formatterParse.apply(source, formatter);
                } catch (Exception ignored) {
                }
            }
        }
        throw new RuntimeException("无法解析'" + source + "'为本地时间日期类型");
    }

    /**
     * 转换为 Date 对象。默认使用 Joda 解析日期字符串，如果解析失败则使用定义好的日期格式来解析为 Date。
     * 可支持 <code>yyyy-MM-dd'T'HH:mm:ss.SSS</code> <code>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</code> 和 {@link #DATE_FORMATS} 中定义的格式。
     *
     * @param source 日期字符串
     * @return Date
     */
    @Nullable
    public static Date convertDate(@NonNull String source) {
        source = source.trim();
        if (source.isEmpty()) {
            return null;
        }
        try {
            return new DateTime(source).toDate();
        } catch (Exception e) {
            return parseDate(source);
        }
    }

    /**
     * 转换为 DateTime 对象。默认使用 Joda 解析日期字符串，如果解析失败则使用定义好的日期格式来解析为 Date 再转换为 DateTime。
     * 可支持 <code>yyyy-MM-dd'T'HH:mm:ss.SSS</code> <code>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</code> 和 {@link #DATE_FORMATS} 中定义的格式。
     *
     * @param source 日期字符串
     * @return DateTime
     */
    @Nullable
    public static DateTime convertDateTime(@NonNull String source) {
        source = source.trim();
        if (source.isEmpty()) {
            return null;
        }
        try {
            return new DateTime(source);
        } catch (Exception e) {
            return new DateTime(parseDate(source));
        }
    }
}
